﻿// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Admin.NET.Core.Service;
using Aliyun.OSS.Util;
using Furion.FriendlyException;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;
using NewLife;
using NewLife.Caching;
using Yitter.IdGenerator;

namespace Admin.NET.Application.Service;

/// <summary>
/// 远程文件服务 🧩
/// </summary>
[ApiDescriptionSettings(ApplicationConst.GroupName, Name = "RemoteFile", Order = 200)]
public class RemoteFileService : IDynamicApiController
{


    private readonly UserManager _userManager;
    private readonly SqlSugarRepository<SysFile> _sysFileRep;
    private readonly OSSProviderOptions _OSSProviderOptions;
    private readonly UploadOptions _uploadOptions;
    private readonly INamedServiceProvider<ICustomFileProvider> _namedServiceProvider;
    private readonly ICustomFileProvider _customFileProvider;
    private static ICacheProvider _cacheProvider;
    private readonly HttpContext _httpContext;

    public RemoteFileService(UserManager userManager,
        SqlSugarRepository<SysFile> sysFileRep,
        INamedServiceProvider<ICustomFileProvider> namedServiceProvider,
        IOptions<OSSProviderOptions> ossProviderOptions,
        ICacheProvider cacheProvider,
        IHttpContextAccessor httpContextAccessor,
        IOptions<UploadOptions> uploadOptions) {
        _namedServiceProvider = namedServiceProvider;
        _userManager = userManager;
        _sysFileRep = sysFileRep;
        _OSSProviderOptions = ossProviderOptions.Value;
        _cacheProvider = cacheProvider;
        _uploadOptions = uploadOptions.Value;
        _httpContext = httpContextAccessor.HttpContext;
        if (_OSSProviderOptions.Enabled) {
            _customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(OSSFileProvider));
        } else if (App.Configuration["SSHProvider:Enabled"].ToBoolean()) {
            _customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(SSHFileProvider));
        } else {
            _customFileProvider = _namedServiceProvider.GetService<ITransient>(nameof(DefaultFileProvider));
        }
    }

    #region 1.0 获取远程文件上传地址
    [HttpPost("RemoteQrcode")]
    public object GetRemoteQrcodeAsync([FromQuery] string id) {

        string host = String.Format("{0}://{1}", _httpContext.Request.Scheme, _httpContext.Request.Host.Value);
        string token = _httpContext.Request.Headers.Authorization;
        if (String.IsNullOrEmpty(id)) {
            id = SnowFlakeSingle.Instance.NextId().ToString();
        }
        List<dynamic> files = new List<dynamic>();
        _cacheProvider.Cache.Set(id, files, 15 * 60); // 5分钟有效期
        StringBuilder builder = new StringBuilder();
        builder.Append("https://m.kmdc.org.cn/");
        builder.Append("#/pages/about/uploadFile");
        builder.AppendFormat("?id={0}", id);
        builder.AppendFormat("&host={0}", host);
        builder.AppendFormat("&token={0}", token);
        return new { id, url = builder.ToString() };
    }
    #endregion

    #region 2.0 提供请求端轮询查询是否有文件成功
    [HttpGet("{id}")]
    public async Task<List<dynamic>> GetUploadFileAsync(string id) {
        List<dynamic> files = _cacheProvider.Cache.Get<List<dynamic>>(id);
        // 取出后清空，防止重复取
        _cacheProvider.Cache.Set(id, new List<dynamic>());
        await Task.CompletedTask;
        return files;
    }
    #endregion

    #region 3.0 移动端上传文件
    /// <summary>
    /// 上传文件 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <param name="id"></param>
    /// <param name="targetPath">存储目标路径</param>
    /// <returns></returns>
    [DisplayName("上传文件")]
    public async Task<SysFile> UploadFile([FromForm] UploadFileInput input, [FromQuery] string id, [BindNever] string targetPath = "") {
        if (String.IsNullOrEmpty(id)) {
            throw Oops.Oh("参数[id]不能为空！");
        }

        // 上传后的文件信息放入缓存，供请求端轮询获取 
        List<dynamic> files = _cacheProvider.Cache.Get<List<dynamic>>(id);
        if (files== null) {
            throw Oops.Oh("本次请求已过期！");
        }
        if (input.File == null || input.File.Length <= 0) throw Oops.Oh(ErrorCodeEnum.D8000);

        if (input.File.FileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) throw Oops.Oh(ErrorCodeEnum.D8005);

        // 判断是否重复上传的文件
        var sizeKb = input.File.Length / 1024; // 大小KB
        var fileMd5 = string.Empty;
        if (_uploadOptions.EnableMd5) {
            await using (var fileStream = input.File.OpenReadStream()) {
                fileMd5 = OssUtils.ComputeContentMd5(fileStream, fileStream.Length);
            }
            // Mysql8 中如果使用了 utf8mb4_general_ci 之外的编码会出错，尽量避免在条件里使用.ToString()
            // 因为 Squsugar 并不是把变量转换为字符串来构造SQL语句，而是构造了CAST(123 AS CHAR)这样的语句，这样这个返回值是utf8mb4_general_ci，所以容易出错。
            var sysFile = await _sysFileRep.GetFirstAsync(u => u.FileMd5 == fileMd5 && u.SizeKb == sizeKb);
            if (sysFile != null) return sysFile;
        }

        // 验证文件类型
        if (!_uploadOptions.ContentType.Contains(input.File.ContentType)) throw Oops.Oh($"{ErrorCodeEnum.D8001}:{input.File.ContentType}");

        // 验证文件大小
        if (sizeKb > _uploadOptions.MaxSize) throw Oops.Oh($"{ErrorCodeEnum.D8002}，允许最大：{_uploadOptions.MaxSize}KB");

        // 获取文件后缀
        var suffix = Path.GetExtension(input.File.FileName).ToLower(); // 后缀
        if (string.IsNullOrWhiteSpace(suffix))
            suffix = string.Concat(".", input.File.ContentType.AsSpan(input.File.ContentType.LastIndexOf('/') + 1));
        if (!string.IsNullOrWhiteSpace(suffix)) {
            //var contentTypeProvider = FS.GetFileExtensionContentTypeProvider();
            //suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key;
            // 修改 image/jpeg 类型返回的 .jpeg、jpe 后缀
            if (suffix == ".jpeg" || suffix == ".jpe")
                suffix = ".jpg";
        }
        if (string.IsNullOrWhiteSpace(suffix)) throw Oops.Oh(ErrorCodeEnum.D8003);

        // 防止客户端伪造文件类型
        if (!string.IsNullOrWhiteSpace(input.AllowSuffix) && !input.AllowSuffix.Contains(suffix)) throw Oops.Oh(ErrorCodeEnum.D8003);
        //if (!VerifyFileExtensionName.IsSameType(file.OpenReadStream(), suffix)) throw Oops.Oh(ErrorCodeEnum.D8001);

        // 文件存储位置
        var path = string.IsNullOrWhiteSpace(targetPath) ? _uploadOptions.Path : targetPath;
        path = path.ParseToDateTimeForRep();

        var newFile = input.Adapt<SysFile>();
        newFile.Id = YitIdHelper.NextId();
        newFile.BucketName = _OSSProviderOptions.Enabled ? _OSSProviderOptions.Bucket : "Local"; // 阿里云对bucket名称有要求，1.只能包括小写字母，数字，短横线（-）2.必须以小写字母或者数字开头  3.长度必须在3-63字节之间
        newFile.FileName = Path.GetFileNameWithoutExtension(input.File.FileName);
        newFile.Suffix = suffix;
        newFile.SizeKb = sizeKb;
        newFile.FilePath = path;
        newFile.FileMd5 = fileMd5;

        var finalName = newFile.Id + suffix; // 文件最终名称
        newFile = await _customFileProvider.UploadFileAsync(input.File, newFile, path, finalName);
        await _sysFileRep.AsInsertable(newFile).ExecuteCommandAsync();
        string host = String.Format("{0}://{1}/", _httpContext.Request.Scheme, _httpContext.Request.Host.Value);
        files.Add(new {
            id = newFile.Id,
            name = newFile.FileName,
            size = newFile.SizeKb,
            url = host + newFile.Url,
        });
        _cacheProvider.Cache.Set(id, files, 15 * 60);
        return newFile;
    }
    #endregion

}
